#include "UserTable.h"

#include "EventFileDriver.h"
#include "EventNode.h"
#include "UserItem.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

struct _UserTable
{
	EventFileDriver parent_instance;
	/* phone 为键的表，用于根据 phone 查询 password */
	EventHash *pw_table;
	/* UserID 为键的表 */
	EventHash *id_table;
};

static int
user_table_parser(EventFileDriver *driver, const epointer data, int length)
{
	EventList *list = event_list_new_with_raw(data);
	EventNode *node = EVENT_NODE(list);
	UserItem  *item;
	UserTable *self = USER_TABLE(driver);

	if (!list)
		return 1;

	/* 引用数据节省开销 */
	item = user_item_new();
	user_item_set_phone(item, (euint64)node->data);
	node = node->next;
	user_item_set_password(item, EVENT_STRING(node->data));
	node = node->next;
	user_item_set_user_name(item, EVENT_STRING(node->data));
	node = node->next;
	user_item_set_user_id(item, *((euint32 *)&node->data));
	node = node->next;
	user_item_set_group_list(item, EVENT_LIST(node->data));

	event_hash_set(self->pw_table, EPTR(user_item_get_phone(item)), item,
		       event_object_unref);
	event_hash_set(self->id_table,
		       EPTR((euint64)user_item_get_user_id(item)),
		       EO_REF(item, void), event_object_unref);
	event_object_unref(list);
	return 0;
}

static EventFileDriverPack *
user_table_writer(EventFileDriver *driver)
{
	UserTable *self = USER_TABLE(driver);
	UserItem  *item;
	HashItem_p h_item;
	EventList *list;
	EventRaw  *raw;
	//
	EventFileDriverPack *pack;

	h_item = event_hash_iter_get(self->pw_table);
	if (!h_item)
		return NULL;

	item = (UserItem *)event_hash_item_get_value(h_item);
	list = event_list_new();
	event_list_add_data(list, EPTR(user_item_get_phone(item)),
			    EVENT_TYPE_UINT64, NULL);
	event_list_add_data(list, EO_REF(user_item_get_password(item), void),
			    EVENT_TYPE_STRING, event_object_unref);
	event_list_add_data(list, user_item_dup_user_name(item),
			    EVENT_TYPE_STRING, event_object_unref);
	event_list_add_data(list, EPTR((euint64)user_item_get_user_id(item)),
			    EVENT_TYPE_UINT32, NULL);
	event_list_add_data(list, EO_REF(user_item_get_group_list(item), void),
			    EVENT_TYPE_LIST, event_object_unref);

	raw = event_list_export_raw(list);
	event_object_unref(list);
	if (!raw)
		return_val_if_fail(raw != NULL, user_table_writer, NULL);

	pack = event_file_driver_pack_new_from_raw(raw);
	return pack;
}

static int
user_table_prepare_data(EventFileDriver *driver)
{
	UserTable *self = USER_TABLE(driver);

	return_val_if_fail(self != NULL, user_table_prepare_data,
			   EVENT_FILE_DRIVER_PREPARE_DATA_ERROR);

	event_hash_iter_reset(self->pw_table);
	event_file_driver_flush_items_num(
		driver, (eint32)event_hash_get_used_size(self->pw_table));
	return EVENT_FILE_DRIVER_SUCCESS;
}

static void
user_table_finalize(UserTable *self)
{
	event_file_driver_finalize(EVENT_FILE_DRIVER(self));
	event_object_unref(self->pw_table);
	event_object_unref(self->id_table);
}

UserTable *
user_table_new(void)
{
	UserTable *object = MALLOC_NEW(UserTable);

	event_file_driver_init(EVENT_FILE_DRIVER(object));
	event_object_set_finalize(object, FINALIZE(user_table_finalize));

	event_file_driver_set_parser(EVENT_FILE_DRIVER(object),
				     user_table_parser);
	event_file_driver_set_writer(EVENT_FILE_DRIVER(object),
				     user_table_writer,
				     user_table_prepare_data);
	event_file_driver_set_file_not_exist_throw(EVENT_FILE_DRIVER(object),
						   false);
	object->pw_table = event_hash_new(EVENT_HASH_MODEL_NUMBER);
	object->id_table = event_hash_new(EVENT_HASH_MODEL_NUMBER);
	return object;
}

euint32
user_table_get_uid_from_phone(UserTable *self, euint64 phone)
{
	UserItem *item;
	return_val_if_fail(self != NULL, user_table_get_uid_from_phone, 0);
	item = (UserItem *)event_hash_get(self->pw_table, EPTR(phone));
	if (!item)
		return 0;
	return user_item_get_user_id(item);
}

euint64
user_table_get_phone_from_uid(UserTable *self, euint32 uid)
{
	UserItem *item;
	return_val_if_fail(self != NULL, user_table_get_phone_from_uid, 0);
	item = (UserItem *)event_hash_get(self->id_table, EPTR((euint64)uid));
	if (!item)
		return 0;
	return user_item_get_phone(item);
}

UserItem *
user_table_get_uitem_from_phone(UserTable *self, euint64 phone)
{
	return_val_if_fail(self != NULL, user_table_get_uitem_from_phone, NULL);
	return USER_ITEM(event_hash_get(self->pw_table, EPTR(phone)));
}

UserItem *
user_table_get_uitem_from_uid(UserTable *self, euint32 uid)
{
	return_val_if_fail(self != NULL, user_table_get_uitem_from_uid, NULL);
	return USER_ITEM(event_hash_get(self->id_table, EPTR((euint64)uid)));
}

static EventList *
create_initialize_group_list(void)
{
	EventList *list = event_list_new();
	/* 添加 GroupID 为 1 的系统事件组节点 */
	event_list_add_data(list, EPTR((euint64)1), EVENT_TYPE_UINT32, NULL);
	return list;
}

static euint32
random_user_id(euint64 phone)
{
	time_t	       now_time;
	static euint64 already = 0;
	if (phone == 0) {
		already = 0;
		return 0;
	}
	now_time = time(NULL);
	if (already == 0)
		already = phone;
	else
		already += (euint32)(now_time % 8999501);
	return (euint32)(already % 8999509);
}

euint32
user_table_add_initialize_user(UserTable *self, euint64 phone,
			       EventString *user_name, EventString *password)
{
	UserItem  *item, *tmp;
	EventList *init_list;
	euint32	   uid;
	return_val_if_fail(self != NULL, user_table_add_initialize_user, 0);

	item = (UserItem *)event_hash_get(self->pw_table, EPTR(phone));
	if (item) {
		event_hash_remove(self->pw_table, EPTR(phone));
		event_hash_remove(self->id_table,
				  EPTR((euint64)user_item_get_user_id(item)));
	}
	item = user_item_new();

	do {
		uid = random_user_id(phone) + 1000000;
		tmp = event_hash_get(self->id_table, EPTR((euint64)uid));
	} while (tmp);
	random_user_id(0);

	user_item_set_user_id(item, uid);
	user_item_set_user_name(item, user_name);
	user_item_set_password(item, password);
	user_item_set_phone(item, phone);
	init_list = create_initialize_group_list();
	user_item_set_group_list(item, init_list);
	event_object_unref(init_list);

	event_hash_set(self->pw_table, EPTR(phone), item, event_object_unref);
	event_hash_set(self->id_table, EPTR((euint64)uid), EO_REF(item, void),
		       event_object_unref);
	return uid;
}

bool
user_table_verify_user_exist(UserTable *self, euint32 user_id)
{
	UserItem *u_item;
	return_val_if_fail(self != NULL, user_table_verify_user_exist, false);
	u_item = USER_ITEM(
		event_hash_get(self->id_table, EPTR((euint64)user_id)));
	return u_item != NULL;
}

void
user_table_add_group_item(UserTable *self, euint32 user_id, euint32 group_id)
{
	UserItem *u_item;
	return_if_fail(self != NULL, user_table_add_group_item);
	u_item = USER_ITEM(
		event_hash_get(self->id_table, EPTR((euint64)user_id)));
	return_if_fail(u_item != NULL, user_table_add_group_item);
	user_item_add_group_id(u_item, group_id);
}

void
__user_table_add_item(UserTable *self, euint64 phone, EventString *password,
		      EventString *user_name, euint32 user_id, EventList *list)
{
	UserItem *item;
	return_if_fail(self != NULL, __user_table_add_item);

	item = event_hash_get(self->pw_table, EPTR(phone));

	if (!item) {
		item = user_item_new();
		event_hash_set(self->pw_table, EPTR(phone), item,
			       event_object_unref);
		/**
                 * 内存泄漏点：同一 UserItem 对象会被释放，则需要
                 * 进行引用，以下 item 若未使用 EO_REF 引用则将引起
                 * finalize 时的 double free。
                 */
		event_hash_set(self->id_table, (EPTR((euint64)user_id)),
			       EO_REF(item, void), event_object_unref);
		event_file_driver_add_count_once(EVENT_FILE_DRIVER(self));
	}
	user_item_set_phone(item, phone);
	user_item_set_password(item, password);
	user_item_set_user_name(item, user_name);
	user_item_set_user_id(item, user_id);
	user_item_set_group_list(item, list);
}

UserItem *
__user_table_from_id_get_item(UserTable *self, euint32 user_id)
{
	return_val_if_fail(self != NULL, __user_table_from_id_get_item, NULL);

	return USER_ITEM(
		event_hash_get(self->id_table, EPTR((euint64)user_id)));
}

EventHash *
__user_table_get_hash_table(UserTable *self, int index)
{
	return_val_if_fail(self != NULL, __user_table_get_hash_table, NULL);
	return index ? self->id_table : self->pw_table;
}
